package com.kalaqia.arouter.compiler.processor;

import com.alibaba.android.arouter.facade.annotation.Autowired;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.facade.model.RouteMeta;
import com.google.auto.service.AutoService;
import com.kalaqia.arouter.compiler.utils.TypeUtils;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import static com.kalaqia.arouter.compiler.utils.Consts.ACTIVITY;
import static com.kalaqia.arouter.compiler.utils.Consts.ANNOTATION_TYPE_AUTOWIRED;
import static com.kalaqia.arouter.compiler.utils.Consts.ANNOTATION_TYPE_ROUTE;
import static com.kalaqia.arouter.compiler.utils.Consts.KEY_MODULE_NAME;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static javax.lang.model.element.Modifier.PUBLIC;

/**
 * function:
 * project:ARouterLibrary
 * describe:
 * created owner:kalaqia 421407393@qq.com
 * created date:2017/9/15 14:36
 */
//<editor-fold desc="version update log">
// updare owner:kalaqia  421407393@qq.com
// update datetime:2017/9/15 14:36 
// update remark:
//</editor-fold>
@AutoService(Processor.class)
@SupportedOptions(KEY_MODULE_NAME)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class KRouterProcessor extends RouteProcessor {

    private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.
    //    private Map<String, String> rootMap = new TreeMap<>();  // Map of root metas, used for generate class file in order.
    private Filer mFiler;       // File util, write class file into disk.
    //    private Logger logger;
    private Types types;
    private Elements elements;
    private TypeUtils typeUtils;
//    private String moduleName = null;   // Module name, maybe its 'app' or others
//    private TypeMirror iProvider = null;

    /**
     * Initializes the processor with the processing environment by
     * setting the {@code processingEnv} field to the value of the
     * {@code processingEnv} argument.  An {@code
     * IllegalStateException} will be thrown if this method is called
     * more than once on the same object.
     *
     * @param processingEnv environment to access facilities the tool framework
     *                      provides to the processor
     * @throws IllegalStateException if this method is called more than once.
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();                  // Generate class.
        types = processingEnv.getTypeUtils();            // Get type utils.
        elements = processingEnv.getElementUtils();      // Get class meta.
        typeUtils = new TypeUtils(types, elements);
    }

    /**
     * {@inheritDoc}
     *
     * @param annotations
     * @param roundEnv
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                this.parseKRoutes(routeElements);
            } catch (Exception e) {
                //                logger.error(e);
            }
        }
        return super.process(annotations, roundEnv);
    }

    private void parseKRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {

            TypeMirror type_Activity = elements.getTypeElement(ACTIVITY).asType();
            TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("KRouter")
//                            .addJavadoc(WARNING_TIPS)
//                            .addSuperinterface(ClassName.get(elements.getTypeElement(ITROUTE_ROOT)))
                    .addModifiers(PUBLIC);
            //  Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
            for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
//                RouteMeta routeMete = null;

                if (types.isSubtype(tm, type_Activity)) {                 // Activity
////                    logger.info(">>> Found activity route: " + tm.toString() + " <<<");
//                    // Get all fields annotation by @Autowired
//                    Map<String, Integer> paramsType = new HashMap<>();


////                    routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                    ;

                    typeSpecBuilder.addMethod(builderMethod(route.path(), element));
                }

//                categories(routeMete);
                // if (StringUtils.isEmpty(moduleName)) {   // Hasn't generate the module name.
                //     moduleName = ModuleUtils.generateModuleName(element, logger);
                // }


            }
//                }
//            }

            // Write router root class into disk
            JavaFile.builder("com.alibaba.android.arouter.routes",
                    typeSpecBuilder.build()
            ).build().writeTo(mFiler);

        }
    }

    private String getPath(String path) {
        return "goTo" + path.replaceAll("/", "_");
    }

    private MethodSpec builderMethod(String path, Element element) {
        String name = getPath(path);
        MethodSpec.Builder goToMethodOfRootBuilder = MethodSpec.methodBuilder(name)
                .returns(ClassName.get("com.alibaba.android.arouter.facade", "Postcard"))
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC);
//                    .addTypeVariable();
//                    .addParameter(rootParamSpec);

//            if (MapUtils.isNotEmpty(rootMap)) {
        // Generate root meta by group name, it must be generated before root, then I can find out the class of group.
//                for (Map.Entry<String, String> entry : rootMap.entrySet()) {
        goToMethodOfRootBuilder.addStatement(
                "$T postcard = $T.getInstance().build($S)"
                , ClassName.get("com.alibaba.android.arouter.facade", "Postcard")
                , ClassName.get("com.alibaba.android.arouter.launcher", "ARouter")
                , path
        );
        goToMethodOfRootBuilder.addStatement("return postcard");

        for (Element field : element.getEnclosedElements()) {
            if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null) {
                // It must be field, then it has annotation, but it not be provider.
                Autowired paramConfig = field.getAnnotation(Autowired.class);
                if (paramConfig.required()) {


//                  typeUtils.typeExchange(field);

//                    goToMethodOfRootBuilder.addParameters(
//                            ParameterSpec.builder(
//                                    null
//                                    , StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name()
//
//                            ).build()
//                    );
                }

            }
        }

        return goToMethodOfRootBuilder.build();
    }


}
